<?php
declare(strict_types=1);

namespace App\Middleware;

use App\Cache\CacheKey;
use App\Enum\Code;
use App\Exception\BusinessException;
use Hyperf\Context\Context;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\Redis;
use HyperfExt\Jwt\Jwt;
use HyperfExt\Jwt\Payload;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AuthMiddleware implements MiddlewareInterface
{
    #[Inject]
    protected Jwt $jwt;

    #[Inject]
    protected Redis $redis;

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $path = $request->getUri()->getPath();

        $this->recordRequestCount();
        
        // 检查是否是后台接口
        if (str_starts_with($path, '/api/admin')) {
            // 排除登录接口
            if ($path === '/api/admin/auth/login') {
                return $handler->handle($request);
            }
            
            // 验证后台token
            $token = $this->getTokenFromRequest($request);

            $payload = $this->validateToken($token, true);
            $this->recordActiveUser($payload);
        } elseif (str_starts_with($path, '/api')) {
            if ($path === '/api/v1/auth/login' || $path === '/api/web/v1/auth/register' || $path === '/api/web/v1/auth/login') {
                return $handler->handle($request);
            }

            $token = $this->getTokenFromRequest($request);

            $payload = $this->validateToken($token, false);
            $this->recordActiveUser($payload);
        } else {
            $this->recordActiveIp($request->getServerParams()['remote_addr']);
        }

        return $handler->handle($request);
    }

    private function validateToken(?string $token, bool $isBackend): Payload
    {
        if (empty($token)) {
            throw new BusinessException(Code::Unauthorized, 'Token is required');
        }

        try {
            $payload = $this->jwt->check(true);

            $this->checkHaveToNeedRelogin($payload);

            $role = $payload->get('role');

            // 仅在访问后台限制只能有admin角色的用户访问，其他接口所有角色都可以访问
            if ($isBackend && $role !== 'admin') {
                throw new BusinessException(Code::Unauthorized, 'Invalid token type');
            }

            Context::set('uid', $payload->get('sub'));
            Context::set('role', $role);

            return $payload;

        } catch (\Throwable $e) {
            if ($e instanceof BusinessException) {
                throw $e;
            }
            throw new BusinessException(Code::TokenExpired, 'Token is invalid or expired');
        }
    }

    protected function checkHaveToNeedRelogin(Payload $payload)
    {
        $time = $this->redis->get(CacheKey::forbidTokenBefore((int)$payload->get('sub')));
        if ($time) {
            $iat = $payload->get('iat');
            if ($iat < $time) {
                throw new BusinessException(Code::TokenExpired, '需要重新登录');
            }
        }
    }

    protected function getTokenFromRequest(ServerRequestInterface $request): ?string
    {
        $token = $request->getHeaderLine('Authorization');
        if (!empty($token) && preg_match('/Bearer\s*(\S+)\b/i', $token, $matches)) {
            return $matches[1];
        }

        $token = $request->getQueryParams()['token'] ?? null;
        if (!empty($token) && is_string($token)) {
            return $token;
        }

        return null;
    }

    protected function recordActiveUser(Payload $payload)
    {
        $this->redis->zadd(CacheKey::activeUser(), time(), $payload->get('sub'));
    }

    public function recordActiveIp(string $ip)
    {
        $this->redis->zadd(CacheKey::activeIp(), time(), $ip);
    }

    private function recordRequestCount()
    {
        $this->redis->zAdd(CacheKey::requestCountMinute(), time(), uniqid('', true));
    }
}
